/*
 * Copyright (c) 2010 Philip Foulkes <philip.foulkes@gmail.com>
 *
 * This file is free software and is made available under the
 * terms of the GNU General Public License, version 2, or at your
 * option, any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/slab.h>
#include <linux/version.h>
#include <linux/netdevice.h>

#include "mrp_private.h"
#include "mrp_port.h"
#include "mrp_mrpdu.h"

 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)
    #define MRP_PORT_ADD_MUL_ADDRESS(net_device,addr) dev_mc_add(net_device,addr)
    #define MRP_PORT_DEL_MUL_ADDRESS(net_device,addr) dev_mc_del(net_device,addr)
#else
    #define MRP_PORT_ADD_MUL_ADDRESS(net_device,addr) dev_mc_add(net_device,addr,ETH_ALEN,false)
    #define MRP_PORT_DEL_MUL_ADDRESS(net_device,addr) dev_mc_delete(net_device,addr,ETH_ALEN,false)
#endif


/*
 * Receive packet callback
 */
static int mrp_receive (struct sk_buff * skb, struct net_device * dev, struct packet_type * pt, struct net_device * orig_dev)
{
    struct mrp_mrpdu * mrp_mrpdu;
    struct ethhdr * eth_header;
    struct mrp_port * mrp_port;
    int ret;
    
    mrp_port = (struct mrp_port *) pt->af_packet_priv;
    mrp_mrpdu = NULL;
    ret = 0;
    
    mrp_mrpdu = mrp_mrpdu_create_and_init_with_supplied_sk_buff(mrp_port->mrp, skb, 1, mrp_port->mrp->use_attribute_list_length);			
    if (!mrp_mrpdu)
    {
        kfree_skb (skb);
        ret = -ENOMEM;
        goto fail;
    }
    
    eth_header = mrp_mrpdu_get_ethernet_header(mrp_mrpdu);
    if (!eth_header)
    {
        ret = -EINVAL;
        goto fail;
    }
    
    if (memcmp(eth_header->h_dest, mrp_port->application_address, ETH_ALEN) != 0 || mrp_port->ether_type != ntohs(eth_header->h_proto))
    {
        ret = -EINVAL;
        goto fail;
    }
    
    mrp_receive_mrpdu_notify(mrp_port->mrp, mrp_mrpdu);
    return 0;
    
fail:
    MRP_PRINT_INFO("%s %s\n", __FUNCTION__, "failed");
    mrp_mrpdu_destroy (&mrp_mrpdu);
    return ret;
}

struct mrp_port *mrp_port_create_and_init(const u8 application_address[6], u16 ether_type, const char *port_name)
{
    struct mrp_port * mrp_port;

    mrp_port = kzalloc(sizeof(struct mrp_port), GFP_KERNEL);
    if (!mrp_port)
        return NULL;

    mrp_port->net_device = dev_get_by_name(&init_net, port_name);
    if (!mrp_port->net_device) {
        mrp_port_destroy (&mrp_port);
        return NULL;
    }
    
    if (MRP_PORT_ADD_MUL_ADDRESS(mrp_port->net_device, (char *)application_address)) {
        mrp_port_destroy(&mrp_port);
        return NULL;
    }

    memcpy(mrp_port->application_address, application_address, 6);
    mrp_port->ether_type = ether_type;
    mrp_port->packet_type.type = __constant_htons(ether_type);
    mrp_port->packet_type.func = mrp_receive;
    mrp_port->packet_type.af_packet_priv = mrp_port;
    mrp_port->is_packet_reception_started = 0;

    return mrp_port;
}

void mrp_port_destroy (struct mrp_port ** mrp_port)
{
    struct mrp_port * port;

    port = *mrp_port;

    if (*mrp_port)
    {
        if (port->net_device)
            MRP_PORT_DEL_MUL_ADDRESS (port->net_device, (char *) port->application_address);
        mrp_port_stop_packet_reception (port);
        if (port->net_device)
            dev_put (port->net_device);
        kfree (port);
        *mrp_port = NULL;
    }
}

int mrp_port_set_mrp_application (struct mrp_port * mrp_port, struct mrp * mrp)
{
    mrp_port->mrp = mrp;
    return 0;
}

int mrp_port_start_packet_reception (struct mrp_port * mrp_port)
{
    if (!mrp_port->is_packet_reception_started)
    {
        dev_add_pack (&mrp_port->packet_type);
        mrp_port->is_packet_reception_started = 1;
        return 0;
    }
    else
        return -EPERM;
}

int mrp_port_stop_packet_reception (struct mrp_port * mrp_port)
{
    if (mrp_port->is_packet_reception_started)
    {
        dev_remove_pack (&mrp_port->packet_type);
        mrp_port->is_packet_reception_started = 0;
        return 0;
    }
    else
        return -EPERM;
}

int mrp_port_send_mrpdu (struct mrp_port * mrp_port, struct mrp_mrpdu * mrp_mrpdu)
{
    struct sk_buff * skb;
    struct ethhdr * ethhdr;

    skb = mrp_mrpdu_get_sk_buff_clone (mrp_mrpdu);

    if (skb_headroom(skb) < ETH_HLEN)
        return -ENOMEM;

    ethhdr = (struct ethhdr *) skb_push (skb, ETH_HLEN);

    skb_set_network_header(skb, 0);
    skb_set_mac_header(skb, 0);
    skb->mac_len = ETH_HLEN;
    skb->dev = mrp_port->net_device;
    skb->protocol = htons (mrp_port->ether_type);
    skb->pkt_type = PACKET_MULTICAST;
    skb->priority = 1; // what value needs to be used here?

    memcpy (ethhdr->h_dest, mrp_port->application_address, ETH_ALEN);
    memcpy (ethhdr->h_source, mrp_port->net_device->dev_addr, ETH_ALEN);
    ethhdr->h_proto = htons (mrp_port->ether_type);

    return dev_queue_xmit (skb);
}
